軟體架構設計原則一切都是為了下面這兩點,別忘了。
LSP 這個原則比較傾向是在物件導向才會有的設計原則,這也正常畢竟我們在討論的軟體設計原則 SOLID 最開始為『 物件導向設計原則 』,但是在很多地方事實上也通用,不過這個原則就比較算是在物件導向才能用的。
根據 《 Clean Architecture 》這本書提到 1988 年,Barbara Liskov 寫下定義子型態的方式 :
若對型態 S 的每一個物件 o1,都存在一個型態為 T 的件 o2,使得在所有針對 T 編寫的程式 P 中,用 o1 替換 o2 後,程式 P 的行為功能不變,則 S 是 T 的子型態
我覺得比較白話文的說法為 :
在使用父類別的地方,如果替換成子類別,則行為功能不變
為什麼會要有這個準則呢 ? 你想想如果替換成子類別不能用,那是不是就有機率這個子類別實際上,不適合長在這個父類別下,因為子類別已經破壞了父類別的繼承體系,就有可能會在未來發生可能的 Bug。
像我在實務上的確有看過,有個子類別已經違反 LSP,但是因為一些原因與時間很難改,結果導致父類別有很多地方,要為了這個子類別寫特殊判斷,結果特殊判斷和原本的行為打架。
不過在 《 Clean Architecture 》 有提到一個重點 :
LSP 視為指導 『 繼承 』的使用,但是多年來已涉及到介面與實作,它已經演變成更廣泛的軟體原則
也就是說這個原則,已經慢慢變成『 除了繼承以外的準則 』
這個範例是 《 Clean Architecture 》中最有名的正方形與長方形問題,正方形只是長方形長與寬相同的情況,所以這裡範例正方形繼承長方形。
然後這個範例違反 LSP 的情況為,如果是用長方形父類別來計算面積答案就對,而如果改成用正方形來計算面積就會錯。別忘了 LSP 的定義為 :
在使用父類別的地方,如果替換成子類別,則行為功能不變
class Rectangle{
h:number
w:number
setHeight(h): void{}
setWidth(w): void{}
getArea(){
return this.w * this.h
}
}
class Square extends Rectangle{
setHeight(h): void {
this.h = h
this.w = h
}
setWidth(w): void {
this.h = w
this.w = w
}
}
const square = new Square()
square.setHeight(10)
square.setWidth(20)
// 這裡就會有問題,行為和答案不合
const area = square.getArea()
LSP 目前應該是我實務上比較少碰到的,比較大的原因在於比較少用到繼承,主要的原因在於繼承是個依賴性很高的東西,而果真的要用到繼承我也會傾向以『 做什麼 』來設計這個父類別。詳細為什麼不用的原因我覺得這篇文章寫的很清楚了 ~ 可參考
老樣子,來問一下三個問題,來加深記憶
LSP 原則基本上是在討論什麼情況使用繼承會有問題,而事實上很多在研究這個原則時,也慢慢的發現使用繼承的問題。
LSP 讓我連想到以前在討論繼承的可能問題,例如 :